﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Drawing;
using System.Drawing.Design;
using System.Reflection;
using System.Threading;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using Citrix.WorkflowStudio.ActivityLibrary.XenServer.V2;
using Citrix.WorkflowStudio.Common;
using Citrix.WorkflowStudio.CustomActivities.Designers;
using Citrix.WorkflowStudio.CustomActivities.Editors;
using Citrix.WorkflowStudio.CustomActivities.Serializers;
using VNC.RFBProtocolHandling;
using XenAPI;

namespace Citrix.WorkflowStudio.Activities.VNCActivityLibrary
{
    [Designer(typeof(BaseActivityDesigner))]
    [BaseActivityDesigner(ImageResourceName = @"Resources.000_SendConsoleKeys_h32bit_32.png", AssemblyType = typeof(SendConsoleKeys))]
    [ToolboxBitmapAttribute(typeof(SendConsoleKeys), @"Resources.000_SendConsoleKeys_h32bit_16.png")]
    [LocDisplayName("ActivityName_SendConsoleKeys", typeof(SendConsoleKeys))]
    [LocDescription("ActivityDesc_SendConsoleKeys", typeof(SendConsoleKeys))]
    [ActivityTreePath("ActivityTreePath_XenServer_VNC", typeof(SendConsoleKeys))]
    [ActivityValidator(typeof(SendConsoleKeysValidator))]
    [ActivityReferenceAssemblies(new string[] { "XenServer, Version=5.0.0.3, Culture=neutral, PublicKeyToken=102be611e60e8ddc" })]
    [DesignerSerializer(typeof(BindingListSerializer), typeof(WorkflowMarkupSerializer))]
    public partial class SendConsoleKeys : XenServerActivityBase
    {
        #region Private Members
        #endregion
        
        /// <summary>
        /// Set any default values for dependency properties here in the constructor.
        /// </summary>
        #region Constructor
        public SendConsoleKeys()
        {
            InitializeComponent();
            VMInput = null;
        }
        #endregion Constructor

        /// <summary>
        /// Dependency properties are properties that will be bound to other properties within the workflow.
        /// Define dependency properties here.
        /// </summary>
        #region Dependency Properties

        #region VMInput Property
        public static DependencyProperty VMInputProperty = DependencyProperty.Register("VMInput", typeof(object), typeof(SendConsoleKeys));
        [DefaultValue(null)]
        [LocCategory(@"PropCat_Parameters", typeof(SendConsoleKeys))]
        [LocDisplayName("PropName_SendConsoleKeys_VMInput", typeof(SendConsoleKeys))]
        [LocDescription("PropDesc_SendConsoleKeys_VMInput", typeof(SendConsoleKeys))]
        [BindTypes(new Type[] { typeof(CTXVM), typeof(List<CTXVM>) })]
        public Object VMInput
        {
            get
            {
                return base.GetValue(VMInputProperty);
            }
            set
            {
                base.SetValue(VMInputProperty, value);
            }
        }

        #endregion VMInput Property
        #region KeysProperty Property
        public static DependencyProperty KeysProperty = DependencyProperty.Register("Keys", typeof(String), typeof(SendConsoleKeys));
        [DefaultValue("")]
        [Category(@"Parameters")]
        [LocDisplayName("PropName_Keys", typeof(SendConsoleKeys))]
        [LocDescription("PropDesc_Keys", typeof(SendConsoleKeys))]
        [EditorAttribute(typeof(TextEditor), typeof(UITypeEditor))]
        [TextEditorOptions(SingleLineMode=true)]
        public String Keys
        {
            get
            {
                return ((String)(base.GetValue(KeysProperty)));
            }
            set
            {
                base.SetValue(KeysProperty, value);
            }
        }
        #endregion KeysProperty Property
        #endregion Dependency Properties

        /// <summary>
        /// Add any additional code that is used by either the properties or the activity code here
        /// </summary>
        #region Supporting Code
        #endregion Supporting Code

        /// <summary>
        /// The logic for your activity belongs in the Execute method below
        /// </summary>
        #region Activity Execution Logic
        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            // Expand all string properties with the required attribute
            ExpandStringProperties(executionContext.Activity);

            if (VMInput == null)
                throw new ArgumentNullException("");
            List<CTXVM> vmList = VMInput as List<CTXVM>;
            if (vmList == null)
            {
                vmList = new List<CTXVM>();
                CTXVM inputVM = VMInput as CTXVM;
                if (inputVM != null)
                    vmList.Add(inputVM);
                else
                    throw new Exception(String.Format(Properties.Resources.ERR_INVALID_PROPERTY, GlobalUtilities.GetPropertyDisplayName((Activity)this, "VMInput")));
            }

            try
            {
                XenAPI.Session connObject = null;
                //get the session connection
                connObject = GetConnection();

                if (connObject.uuid == null)
                    throw new Exception(Properties.Resources.ERR_InvalidConnection);

                String url = connObject.Url;
                if (url.Contains("https"))
                    url = url.Replace("https://", "");
                else if (url.Contains("http"))
                    url = url.Replace("http://", "");
                String[] urlComponents = url.Split(':');

                foreach (CTXVM ctxVM in vmList)
                {
                    String loc = null;
                    String newloc = null;

                    XenRef<VM> refVM = VM.get_by_uuid(connObject, ctxVM.uuid);
                    VM vmRecord = VM.get_record(connObject, refVM);
                    if (vmRecord.is_a_template || vmRecord.is_control_domain)
                        throw new Exception(Properties.Resources.ERR_IsControlDomainOrTemplate);

                    if(!(vmRecord.power_state == vm_power_state.Running))
                        throw new Exception(String.Format(Properties.Resources.ERR_BadPowerState, vm_power_state.Running, vmRecord.power_state));

                    List<XenRef<XenAPI.Console>> vmConsolesRef = vmRecord.consoles;
                    if(vmConsolesRef == null || vmConsolesRef.Count == 0)
                        throw new Exception(String.Format(Properties.Resources.ERR_NoConsoleFound, vmRecord.name_label));
                    
                    String serverIP = null;
                    foreach (XenRef<XenAPI.Console> vmConsoleRef in vmConsolesRef)
                    {
                        XenAPI.Console vmConsole = XenAPI.Console.get_record(connObject, vmConsoleRef);
                        loc = vmConsole.location;
                        int stIndex = loc.IndexOf("://") + 3;
                        int lastIndex = loc.LastIndexOf('/');
                        if (stIndex > 0 && lastIndex > 0)
                        {
                            serverIP = loc.Substring(stIndex, lastIndex - stIndex);
                        }                        
                        newloc = "CONNECT " + loc.Substring(loc.LastIndexOf('/')) + "&session_id=" + connObject.uuid + " HTTP/1.0\r\n\r\n";
                    }

                    RFBTcpClient rfbClient = new RFBTcpClient();
                    rfbClient.Connect(serverIP == null ? urlComponents[0] : serverIP, Int32.Parse(urlComponents[1])); // connect to port on server
                    rfbClient.NoDelay = true; // sending without delay!
                    rfbClient.GetRFBStream().WriteString(newloc);
                    rfbClient.GetRFBStream().Flush();
                    Thread.Sleep(2000);
                    byte[] retByteArray = new byte[100];
                    int len = rfbClient.GetStream().Read(retByteArray, 0, 100);
                    String retMsg = new System.Text.ASCIIEncoding().GetString(retByteArray, 0, 100);

                    RFBProtocolHandler protocolHandler = new RFBProtocolHandler(rfbClient);
                    

                    // connection established!
                    // getting the protocol-Message from the Server
                    uint serverMajor = 0;
                    if (retMsg.Contains("RFB 003."))
                    {
                        serverMajor = 3;
                    }

                    if (serverMajor > 3)
                    {
                        String ver = null;
                        if (retMsg.Contains("RFB"))
                            ver = retMsg.Substring(retMsg.IndexOf("RFB") + 4, 7);
                        String err = String.Format(Properties.Resources.ERR_ServerVersionMismatch, "003.003", ver);
                        throw new Exception(err);
                    }

                    // sending my protocol-Message to server
                    protocolHandler.sendProtocolMessage(3, 3); // version 3.3

                    // do authentification
                    //  protocolHandler.authenticate(); // throws exception, if failed

                    // send client initalization
                    protocolHandler.sendClientInitialization();

                    // server initalization
                    ServerData serverdata = protocolHandler.receiveServerInitialization();
                    foreach (uint k in Keys)
                    {
                        protocolHandler.sendKeyEvent(k, true);
                        protocolHandler.sendKeyEvent(k, false);
                    }
                    
                    //sending 'carriage return'
                    protocolHandler.sendKeyEvent(65293, true);
                    protocolHandler.sendKeyEvent(65293, false);
                    rfbClient.Close();
                }
            }
            catch (XenAPI.Failure xenEx)
            {
                //throw xenEx.Message == null ? xenEx : new Exception(xenEx.Message, xenEx);
                throw xenEx;//throw the exception unless we know what to do
            }
            return ActivityExecutionStatus.Closed;
        }
        #endregion Activity Execution Logic
    }

    /// <summary>
    /// Property validation code can be supplied below.
    /// In the Validate method you have to create a new instance of the activity class above,
    ///     create a new ValidationErrorCollection instance,
    ///     and then write the code to process the validation.
    ///     For each validation error/warning, you need to add the error/warning to the ValidationErrorCollection.
    ///     Finally, the Validate method should return the ValidationErrorCollection object.
    /// </summary>
    #region Validator
    public class SendConsoleKeysValidator : XenServerActivityBaseValidator
    {
        public static String GetFriendlyName(Activity callingActivity, DependencyProperty dependencyValue)
        {
            try
            {
                Type t = callingActivity.GetType();
                PropertyInfo pi = t.GetProperty(dependencyValue.Name);
                Object[] attrs = pi.GetCustomAttributes(typeof(DisplayNameAttribute), false);

                //take the first matching display name if there is one
                if (attrs.Length > 0)
                    return ((DisplayNameAttribute)attrs[0]).DisplayName;
                else
                    return dependencyValue.Name;
            }
            catch
            {
                return dependencyValue.Name;
            }
        }

        public bool ValidateObjectBindings(Activity callingActivity, DependencyProperty dependencyValue, Object inputValue, ref ValidationErrorCollection errs)
        {
            String friendlyName = GetFriendlyName(callingActivity, dependencyValue);

            //check if there is a valid standard binding
            if (callingActivity.IsBindingSet(dependencyValue))
            {
                //exit without error if the binding is valid else return binding error
                if (!GlobalUtilities.IsValidBinding(callingActivity, dependencyValue))
                    errs.Add(new ValidationError(String.Format(@"The '{0}' property is bound, but the provided binding string is no longer valid.", friendlyName), 101, false, dependencyValue.Name));
                return true;
            }

            //check if the object is Null or empty string
            if (inputValue == null || String.IsNullOrEmpty(inputValue.ToString()))
            {
                errs.Add(new ValidationError(String.Format(@"You must specify a value for '{0}'.", friendlyName), 101, false, dependencyValue.Name));
                return true;
            }

            //check if there is a string binding (we don't currently have a way to validate this type so if it is there just accept it and return
            if (GlobalUtilities.StringContainsBindingString(inputValue.ToString()))
                return true;

            //return false if this doesn't have bindings
            return false;
        }

        public override ValidationErrorCollection Validate(ValidationManager manager, object obj)
        {
            SendConsoleKeys MyActivity = obj as SendConsoleKeys;
            ValidationErrorCollection errs = base.Validate(manager, obj);

            if ((MyActivity != null) && (MyActivity.Parent != null))
            {
                #region Keys
                ValidateObjectBindings(MyActivity, SendConsoleKeys.KeysProperty, MyActivity.Keys, ref errs);
                #endregion Keys

                #region VMInput
                ValidateObjectBindings(MyActivity, SendConsoleKeys.VMInputProperty, MyActivity.VMInput, ref errs);
                #endregion VMInput
            }
            
            // Return the ValidationErrorCollection
            return errs;
        }
    }
    #endregion Validator
}